//// keytest.cpp : Defines the entry point for the console application.
////
#include "stdafx.h"

#include "keydef.h"

HHOOK	g_khook;
//HANDLE  g_hEvent;

//UINT	g_key1=VK_LCONTROL;
//UINT	g_key2=VK_RCONTROL;


std::vector<KB_Struct> switchCases;


struct Settings
{
	Settings() : sound_on(false), broadcast(false), delay(200)
	{}
	bool sound_on;
	bool broadcast;
	int delay;
};

Settings settings;


// default initialization
void initCases()
{
    KB_Struct RusEng;
    RusEng.keyCode = VK_RCONTROL;
    RusEng.layouts.push_back(ENG);
    RusEng.layouts.push_back(RUS);
    switchCases.push_back(RusEng);

    KB_Struct UkrEng;
    UkrEng.keyCode = VK_LCONTROL;
    UkrEng.layouts.push_back(ENG);
    UkrEng.layouts.push_back(UKR);
    switchCases.push_back(UkrEng);
}

// read from registry
bool readSettings()
{
    HKEY rootKey = 0;
    long res = RegOpenCurrentUser(KEY_READ, &rootKey);
    if (res != ERROR_SUCCESS)
        return false;

    HKEY key = 0;
    res = RegOpenKeyEx(rootKey, _T("Software\\Sintegrial\\KeySwitch"), 0, KEY_READ, &key);
    if (res != ERROR_SUCCESS)
    {
        RegCloseKey(rootKey);
        return false;
    }

    BYTE value[1024];
    DWORD len = sizeof(value);
    
    TCHAR name[20];
    int cnt = 1;
    UINT *ptr;
    UINT klid;
    HKL hkl;

    do {
        // get value
        wsprintf(name, _T("Layout%d"), cnt++);
		len = sizeof(value);
        res = RegQueryValueEx(key, name, NULL, NULL, value, &len);
        if (res != 0)
            break;

        if (!len) continue;

        // parse value
        KB_Struct kbs;
        ptr = (UINT*)value;
        kbs.keyCode = *ptr++;
        
        int i = 4;
        while (i < len) {
            i += 4;
            // read klid
            klid = *ptr++;
            // get HKL from it
            wsprintf(name, _T("%08x"), klid);
            hkl = LoadKeyboardLayout(name, 0);
            kbs.layouts.push_back(hkl);
        }

        if (!kbs.layouts.empty())
            switchCases.push_back(kbs);

    } while (true);


	// read sound
	res = RegQueryValueEx(key, _T("Sound"), NULL, NULL, value, &len);
	if (!res)
		settings.sound_on = (*value == 't');

	// read broadcast
	res = RegQueryValueEx(key, _T("Broadcast"), NULL, NULL, value, &len);
	if (!res)
		settings.broadcast = (*value == 't');

	// read delay
	res = RegQueryValueEx(key, _T("Delay"), NULL, NULL, value, &len);
	if (!res)
		settings.delay = *(int*)value;


    RegCloseKey(key);
    RegCloseKey(rootKey);
    return true;
}


BOOL CALLBACK SwitchLangProc(      
    HWND hwnd,
    LPARAM lParam
)
{
	PostMessage(hwnd,WM_INPUTLANGCHANGEREQUEST,0,lParam);
	return true;
}

LRESULT CALLBACK KbdHook(int nCode,WPARAM wParam,LPARAM lParam) 
{
    static HKL lastCode = 0;
    static int lastKey = 0;
    static ULARGE_INTEGER pressTime;
    static BYTE keyStates[256];

    HWND hWnd=GetForegroundWindow();

    if (nCode==HC_ACTION) 
    {
        KBDLLHOOKSTRUCT *ks=(KBDLLHOOKSTRUCT*)lParam;

        // check if more than 1 key pressed
        //GetKeyboardState(keyStates);
        int cnt = 0;
        for (UINT i = 0; i < 256; i++)
        {
            //if (keyStates[i] & 0x80)
			if (GetKeyState(i) & 0x80)
                cnt++;
        }
		//cnt = 1;

        if (wParam==WM_KEYDOWN && cnt == 1)
        {
            // avoid repeat
            if (lastKey && lastKey == ks->vkCode)
            {} else
            {                
                // allow further check only if 1 key pressed
                //if (cnt == 0)
                    for (UINT i = 0; i < switchCases.size(); i++)
                    {
                        KB_Struct &kbs = switchCases[i];
                        if (ks->vkCode==kbs.keyCode)
                        {
                            if (hWnd) 
                            {
                                DWORD act_thread = GetWindowThreadProcessId(hWnd, NULL);
                                HKL id = GetKeyboardLayout(act_thread);
                                HKL newId = kbs.layouts[0];
                                //printf("%d ", id);

                                for (UINT j = 0; j < kbs.layouts.size(); j++)
                                {
                                    if (id == kbs.layouts[j])
                                    {
                                        if (j < kbs.layouts.size()-1)
                                            newId = kbs.layouts[j+1];
                                        break;
                                    }
                                }

                                //PostMessage(hWnd,WM_INPUTLANGCHANGEREQUEST,0,(LPARAM)newId);
                                lastCode = newId;
                                lastKey = kbs.keyCode;

                                FILETIME time;
                                GetSystemTimeAsFileTime(&time);
                                pressTime.HighPart = time.dwHighDateTime;
                                pressTime.LowPart = time.dwLowDateTime;
                            }

                            break;
                        }
                    } // for
            } 
        }

        if (wParam==WM_KEYUP && cnt == 1)
        {
            if (lastKey == ks->vkCode)
            {
                FILETIME time;
                GetSystemTimeAsFileTime(&time);
                ULARGE_INTEGER releaseTime;
                releaseTime.HighPart = time.dwHighDateTime;
                releaseTime.LowPart = time.dwLowDateTime;
                ULONGLONG delta = releaseTime.QuadPart - pressTime.QuadPart;

				// TODO: adjustable
				if (delta < (settings.delay*10000))
                {
                    //SendMessage(hWnd,WM_INPUTLANGCHANGEREQUEST,0,(LPARAM)lastCode);

					// broadcast?
					if (settings.broadcast)
						PostMessage(HWND_BROADCAST,WM_INPUTLANGCHANGEREQUEST,0,(LPARAM)lastCode);
					else {
						DWORD act_thread = GetWindowThreadProcessId(hWnd, NULL);
						EnumThreadWindows(act_thread, SwitchLangProc, (LPARAM)lastCode);
					}

					// TODO: sound effect
					if (settings.sound_on)
					{
						//PlaySound(_T("MouseClick"), NULL, SND_ASYNC);
						Beep(200,20);
					}
                }

                lastCode = 0;
                lastKey = 0;
                pressTime.QuadPart = 0;
            }
        }
    }

    return CallNextHookEx(g_khook,nCode,wParam,lParam);
}

void  failedx(const TCHAR *msg) {
    MessageBox(NULL,msg,_T("Error"),MB_OK|MB_ICONERROR);
    ExitProcess(1);
}

void  failed(const TCHAR *msg) {
    DWORD		fm;
    TCHAR		*msg1,*msg2;
    const TCHAR	*args[2];

    fm=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,GetLastError(),0,(LPTSTR)&msg1,0,NULL);
    if (fm==0)
        ExitProcess(1);
    args[0]=msg;
    args[1]=msg1;
    fm=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_STRING|
        FORMAT_MESSAGE_ARGUMENT_ARRAY,
        _T("%1: %2"),0,0,(LPTSTR)&msg2,0,(va_list*)&args[0]);
    if (fm==0)
        ExitProcess(1);
    MessageBox(NULL,msg2,_T("Error"),MB_OK|MB_ICONERROR);
    ExitProcess(1);
}

void setKeyHook() 
{
    // first, try to get values from the registry
    readSettings();

    // if no, fill with defaults
    if (switchCases.empty())
        initCases();

    // set the hook
    g_khook=SetWindowsHookEx(WH_KEYBOARD_LL,KbdHook,GetModuleHandle(0),0);
    if (g_khook==0)
        failed(_T("SetWindowsHookEx()"));
}

void unsetKeyHook()
{
    UnhookWindowsHookEx(g_khook);
}

void checkKeyStates()
{
	static SHORT lastCode = 0;
	static UINT lastKeyCode = 0;
    static ULARGE_INTEGER pressTime;

    for (UINT i = 0; i < switchCases.size(); i++)
    {
        KB_Struct &kbs = switchCases[i];

		SHORT res = GetAsyncKeyState(kbs.keyCode);

		if (res & 0x80000000)
		{
			// pressed
			lastCode = res;
			lastKeyCode = kbs.keyCode;

            FILETIME time;
            GetSystemTimeAsFileTime(&time);
            pressTime.HighPart = time.dwHighDateTime;
            pressTime.LowPart = time.dwLowDateTime;
			return;
		}
		else
		{
			// released
			if (lastCode != 0 && lastKeyCode == kbs.keyCode && pressTime.QuadPart)
			{
				lastCode = 0;
				lastKeyCode = 0;

                FILETIME time;
                GetSystemTimeAsFileTime(&time);
                ULARGE_INTEGER releaseTime;
                releaseTime.HighPart = time.dwHighDateTime;
                releaseTime.LowPart = time.dwLowDateTime;
                ULONGLONG delta = releaseTime.QuadPart - pressTime.QuadPart;
				pressTime.QuadPart = 0;

				// TODO: adjustable
                if (delta < 2000000)
                {
					HWND hWnd = GetForegroundWindow();
					if (hWnd) 
					{
						DWORD act_thread = GetWindowThreadProcessId(hWnd, NULL);
						HKL id = GetKeyboardLayout(act_thread);
						HKL newId = kbs.layouts[0];
						//printf("%d ", id);

						for (UINT j = 0; j < kbs.layouts.size(); j++)
						{
							if (id == kbs.layouts[j])
							{
								if (j < kbs.layouts.size()-1)
									newId = kbs.layouts[j+1];
								break;
							}
						}

						// send
						EnumThreadWindows(act_thread, SwitchLangProc, (LPARAM)newId);
					}

				}

				break;
			}

		}
	}
}
